
using System;
using Velocity4Net.Ctx;
using Velocity4Net.Errors;
using System.Reflection;
namespace Velocity4Net.Runtime.Parse.AST
{
    /**
     *  This node is responsible for the bracket notation at the end of
     *  a reference, e.g., $foo[1]
     */

    public class ASTIndex : SimpleNode
    {
        private String methodName = "Item";

        /**
         * Indicates if we are running in strict reference mode.
         */
        protected bool strictRef = false;

        public ASTIndex(int i)
            : base(i)
        {

        }

        public ASTIndex(Parser p, int i)
            : base(p, i)
        {

        }

        public override Object Init(InternalContextAdapter context, Object data)
        {
            base.Init(context, data);
            strictRef = rsvc.GetBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false);
            return data;
        }



        private Object[] noParams = { };
        private Type[] noTypes = { };
        /**
         * If argument is an Integer and negative, then return (o.size() - argument). 
         * Otherwise return the original argument.  We use this to calculate the true
         * index of a negative index e.g., $foo[-1]. If no size() method is found on the
         * 'o' object, then we throw an VelocityException.
         * @param context Used to access the method cache.
         * @param node  ASTNode used for error reporting.
         */
        public static Object adjMinusIndexArg(Object argument, Object o,
                                   InternalContextAdapter context, SimpleNode node)
        {
            if (argument is int && ((int)argument) < 0)
            {
                // The index value is a negative number, $foo[-1], so we want to actually
                // Index [size - value], so try and call the size method.
                PropertyInfo method = o.GetType().GetProperty("Count");

                //VelMethod method = ClassUtils.getMethod("size", noParams, noTypes,
                //                   o, context, node, false);
                if (method == null)
                {
                    // The object doesn't have a size method, so there is no notion of "at the end"
                    throw new VelocityException(
                      "A 'Count' property required for negative value "
                       + argument + " does not exist for class '"
                        //+ o.GetType().FullName + "' at " + Log.formatFileString(node)
                       );
                }

                Object size = null;
                try
                {
                    size = method.GetValue(o, new object[] { });
                }
                catch (System.Exception e)
                {
                    throw new VelocityException("Error trying to calls the 'size()' method on '"
                        //+ o.getClass().getName() + "' at " + Log.formatFileString(node)
                        //, e
                      );
                }

                int sizeint = 0;
                try
                {
                    sizeint = (int)size;
                }
                catch (System.Exception e)
                {
                    // If size() doesn't return an Integer we want to report a pretty error
                    throw new VelocityException("Method 'size()' on class '"
                        //+ o.getClass().getName() + "' returned '" + size.getClass().getName()
                        //+ "' when Integer was expected at " + Log.formatFileString(node)
                        );
                }

                argument = sizeint + (int)argument;
            }

            // Nothing to do, return the original argument
            return argument;
        }

        public override Object Execute(Object o, InternalContextAdapter context)
        {
            INode child = children[0];
            Object argument = child.GetValue(context);
            // If negative, turn -1 into size - 1
            argument = adjMinusIndexArg(argument, o, context, this);
            Object[] args = { argument };
            Type[] paramClasses = { argument == null ? null : argument.GetType() };

            //VelMethod method = ClassUtils.getMethod(methodName, args, paramClasses,
            //                                        o, context, this, strictRef);

            PropertyInfo[] methods = o.GetType().GetProperties();
            PropertyInfo method = null;
            foreach (PropertyInfo p in methods)
            {
                ParameterInfo[] pms = p.GetIndexParameters();
                if (pms.Length > 0)
                {
                    if (pms[0].ParameterType.IsAssignableFrom(paramClasses[0]))
                    {
                        method = p;
                        break;
                    }
                }
            }
            if (method == null) return null;

            try
            {
                /*
                 *  get the returned object.  It may be null, and that is
                 *  valid for something declared with a void return type.
                 *  Since the caller is expecting something to be returned,
                 *  as long as things are peachy, we can return an empty
                 *  String so ASTReference() correctly figures out that
                 *  all is well.
                 */
                Object obj = method.GetValue(o, new object[] { argument });

                if (obj == null)
                {
                    return "";
                }

                return obj;
            }
            /**
             * pass through application level runtime exceptions
             */
            catch (System.ApplicationException e)
            {
                throw e;
            }
            catch (System.Exception e)
            {
                //String msg = "Error invoking method 'get("
                //  + (argument == null ? "null" : argument.getClass().getName())
                //  + ")' in " + o.getClass().getName()
                //  + " at " + Log.formatFileString(this);
                //log.error(msg, e);
                //throw new VelocityException(msg, e);
                throw;
            }
        }
    }
}